//
//  SearchViewController.swift
//  HalfTunes
//
//  Created by Ken Toh on 13/7/15.
//  Copyright (c) 2015 Ken Toh. All rights reserved.
//

import UIKit
import MediaPlayer

class SearchViewController: UIViewController {

  @IBOutlet weak var tableView: UITableView!
  @IBOutlet weak var searchBar: UISearchBar!

  var searchResults = [Track]()
    var activeDownloads = [String: Download]()
    
    let defaultSession = URLSession(configuration: URLSessionConfiguration.default)
    var dataTask: URLSessionDataTask?
  
    lazy var downloadSession: URLSession = {
        return URLSession(configuration: URLSessionConfiguration.background(withIdentifier: "LYZBackgroundSession"), delegate: self, delegateQueue: nil)
    }()
    
  lazy var tapRecognizer: UITapGestureRecognizer = {
    var recognizer = UITapGestureRecognizer(target:self, action: #selector(SearchViewController.dismissKeyboard))
    return recognizer
  }()
  
  // MARK: View controller methods
  
  override func viewDidLoad() {
    super.viewDidLoad()
    tableView.tableFooterView = UIView()
  }
  
  override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
  }
  
  // MARK: Handling Search Results
  
  // This helper method helps parse response JSON NSData into an array of Track objects.
  func updateSearchResults(_ data: Data?) {
    searchResults.removeAll()
    do {
      if let data = data, let response = try JSONSerialization.jsonObject(with: data, options:JSONSerialization.ReadingOptions(rawValue:0)) as? [String: AnyObject] {
        
        // Get the results array
        if let array: AnyObject = response["results"] {
          for trackDictonary in array as! [AnyObject] {
            if let trackDictonary = trackDictonary as? [String: AnyObject], let previewUrl = trackDictonary["previewUrl"] as? String {
              // Parse the search result
              let name = trackDictonary["trackName"] as? String
              let artist = trackDictonary["artistName"] as? String
              searchResults.append(Track(name: name, artist: artist, previewUrl: previewUrl))
            } else {
              print("Not a dictionary")
            }
          }
        } else {
          print("Results key not found in dictionary")
        }
      } else {
        print("JSON Error")
      }
    } catch let error as NSError {
      print("Error parsing results: \(error.localizedDescription)")
    }
    
    DispatchQueue.main.async {
      self.tableView.reloadData()
      self.tableView.setContentOffset(CGPoint.zero, animated: false)
    }
  }
  
  // MARK: Keyboard dismissal
  
  func dismissKeyboard() {
    searchBar.resignFirstResponder()
  }
  
  // MARK: Download methods
  
  // Called when the Download button for a track is tapped
  func startDownload(_ track: Track) {
    let urlString = track.previewUrl
    let download: Download = Download(url: urlString!)
    
    download.start(downloadSession: self.downloadSession)
    self.activeDownloads[download.url] = download
  }
  
  // Called when the Pause button for a track is tapped
  func pauseDownload(_ track: Track) {
    if let url = track.previewUrl, let download = activeDownloads[url] {
        if download.isDownloading {
            download.downloadTask?.cancel(byProducingResumeData: { (data) in
                download.resumeData = data
            })
            download.isDownloading = false
        }
    }
  }
  
  // Called when the Cancel button for a track is tapped
  func cancelDownload(_ track: Track) {
    if let url = track.previewUrl, let download = activeDownloads[url] {
        download.downloadTask?.cancel()
        activeDownloads[url] = nil
    }
  }
  
  // Called when the Resume button for a track is tapped
  func resumeDownload(_ track: Track) {
    if let urlString = track.previewUrl, let download = activeDownloads[urlString] {
        if let resumeData = download.resumeData {
            download.downloadTask = self.downloadSession.downloadTask(withResumeData: resumeData)
            download.downloadTask?.resume()
            download.isDownloading = true
        }else if let url = URL(string: urlString) {
            download.downloadTask = self.downloadSession.downloadTask(with: url)
            download.downloadTask?.resume()
            download.isDownloading = true
        }
    }
  }
  
   // This method attempts to play the local file (if it exists) when the cell is tapped
  func playDownload(_ track: Track) {
    if let urlString = track.previewUrl, let url = localFilePathForUrl(urlString) {
      let moviePlayer:MPMoviePlayerViewController! = MPMoviePlayerViewController(contentURL: url)
      presentMoviePlayerViewControllerAnimated(moviePlayer)
    }
  }
  
  // MARK: Download helper methods
  
  // This method generates a permanent local file path to save a track to by appending
  // the lastPathComponent of the URL (i.e. the file name and extension of the file)
  // to the path of the app’s Documents directory.
  func localFilePathForUrl(_ previewUrl: String) -> URL? {
    let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as NSString

    if let url = URL(string: previewUrl), let lastPathComponent = Optional<String>(url.lastPathComponent) {
        let fullPath = documentsPath.appendingPathComponent(lastPathComponent)
        return URL(fileURLWithPath:fullPath)
    }
    return nil
  }
  
  // This method checks if the local file exists at the path generated by localFilePathForUrl(_:)
  func localFileExistsForTrack(_ track: Track) -> Bool {
    if let urlString = track.previewUrl, let localUrl = localFilePathForUrl(urlString) {
      var isDir : ObjCBool = false
      if let path = Optional<String>(localUrl.path) {
        return FileManager.default.fileExists(atPath: path, isDirectory: &isDir)
      }
    }
    return false
  }
    
    func trackIndexForDownloadTask(downloadTask: URLSessionDownloadTask?) -> Int {
        let url = downloadTask?.originalRequest?.url?.absoluteString
        if url != nil {
            for (index, track) in searchResults.enumerated() {
                if track.previewUrl == url {
                    return index
                }
            }
        }
        
        return -1
    }
}

// MARK: - UISearchBarDelegate

extension SearchViewController: UISearchBarDelegate {
  func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
    // Dimiss the keyboard
    dismissKeyboard()
    
    // TODO
    if searchBar.text!.isEmpty {
        return
    }
    
    if dataTask != nil {
        dataTask?.cancel();
    }
    
    UIApplication.shared.isNetworkActivityIndicatorVisible = true
    
    let searchTerm = searchBar.text!.addingPercentEncoding(withAllowedCharacters: NSCharacterSet.urlQueryAllowed)
    let url = URL(string: "https://itunes.apple.com/search?media=music&entity=song&term=\(searchTerm!)")
    
    dataTask = defaultSession.dataTask(with: url!, completionHandler: { (data, response, error) in
        DispatchQueue.main.async() {
            UIApplication.shared.isNetworkActivityIndicatorVisible = false
        }
        
        let httpResponse = response as? HTTPURLResponse
        if error != nil {
            print(error!.localizedDescription)
        }else if httpResponse != nil {
            if httpResponse!.statusCode == 200 {
                self.updateSearchResults(data)
            }
        }
    })
    
    dataTask?.resume()
  }
    
  func position(for bar: UIBarPositioning) -> UIBarPosition {
    return .topAttached
  }
    
  func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
    view.addGestureRecognizer(tapRecognizer)
  }
    
  func searchBarTextDidEndEditing(_ searchBar: UISearchBar) {
    view.removeGestureRecognizer(tapRecognizer)
  }
}

// MARK: TrackCellDelegate

extension SearchViewController: TrackCellDelegate {
  func pauseTapped(_ cell: TrackCell) {
    if let indexPath = tableView.indexPath(for: cell) {
      let track = searchResults[indexPath.row]
      pauseDownload(track)
      tableView.reloadRows(at: [IndexPath(row: indexPath.row, section: 0)], with: .none)
    }
  }
  
  func resumeTapped(_ cell: TrackCell) {
    if let indexPath = tableView.indexPath(for: cell) {
      let track = searchResults[indexPath.row]
      resumeDownload(track)
      tableView.reloadRows(at: [IndexPath(row: indexPath.row, section: 0)], with: .none)
    }
  }
  
  func cancelTapped(_ cell: TrackCell) {
    if let indexPath = tableView.indexPath(for: cell) {
      let track = searchResults[indexPath.row]
      cancelDownload(track)
      tableView.reloadRows(at: [IndexPath(row: indexPath.row, section: 0)], with: .none)
    }
  }
  
  func downloadTapped(_ cell: TrackCell) {
    if let indexPath = tableView.indexPath(for: cell) {
      let track = searchResults[indexPath.row]
      startDownload(track)
      tableView.reloadRows(at: [IndexPath(row: indexPath.row, section: 0)], with: .none)
    }
  }
}

// MARK: UITableViewDataSource

extension SearchViewController: UITableViewDataSource {
  func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return searchResults.count
  }
  
  func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "TrackCell", for: indexPath) as!TrackCell
    
    // Delegate cell button tap events to this view controller
    cell.delegate = self
    
    let track = searchResults[indexPath.row]
    
    // Configure title and artist labels
    cell.titleLabel.text = track.name
    cell.artistLabel.text = track.artist

    // If the track is already downloaded, enable cell selection and hide the Download button
    let downloaded = localFileExistsForTrack(track)
    cell.selectionStyle = downloaded ? UITableViewCellSelectionStyle.gray : UITableViewCellSelectionStyle.none
    
    if downloaded {
        cell.progressView.isHidden = true
        cell.progressLabel.isHidden = true
        
        cell.downloadButton.isHidden = true
        cell.pauseButton.isHidden = true
        cell.cancelButton.isHidden = true
    }else {
        if let activeDownload = activeDownloads[track.previewUrl!] {
            
            if let totalSizeString = activeDownload.totalSizeString {
                let progerssString = String(format: "%.1f%% of %@", activeDownload.progress * 100.0, totalSizeString)
                cell.progressLabel.text = progerssString
                cell.progressLabel.isHidden = false
            }else {
                cell.progressLabel.isHidden = true
            }
            
            cell.progressView.progress = activeDownload.progress
            cell.progressView.isHidden = false
            
            cell.downloadButton.isHidden = true
            cell.pauseButton.isHidden = false
            cell.cancelButton.isHidden = false
            
            var title: String
            if activeDownload.isDownloading {
                title = "Pause"
            }else {
                title = "Resume"
            }
            
            cell.pauseButton.setTitle(title, for: .normal)
        }else {
            cell.downloadButton.isHidden = false
            cell.pauseButton.isHidden = true
            cell.cancelButton.isHidden = true
            
            cell.progressView.isHidden = true
            cell.progressLabel.isHidden = true
        }
    }
    
    return cell
  }
}

// MARK: UITableViewDelegate

extension SearchViewController: UITableViewDelegate {
  func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    return 62.0
  }
  
  func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    let track = searchResults[indexPath.row]
    if localFileExistsForTrack(track) {
      playDownload(track)
    }
    tableView.deselectRow(at: indexPath, animated: true)
  }
}

extension SearchViewController: URLSessionDownloadDelegate {
    func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
        if error != nil {
            var errorString = "Download error"
            
            if let detailString = error?.localizedDescription {
                errorString += ": \(detailString)"
            }
            print(errorString)
        }
    }
    
    /* Sent when a download task that has completed a download.  The delegate should
     * copy or move the file at the given location to a new location as it will be
     * removed when the delegate message returns. URLSession:task:didCompleteWithError: will
     * still be called.
     */
    
    func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
        print("Finished downloading: \(location)")
        
        if let remoteUrl = downloadTask.originalRequest?.url?.absoluteString, let destinationPath = self.localFilePathForUrl(remoteUrl) {
            let fileManager = FileManager()

            do {
                try fileManager.removeItem(at: destinationPath)
            }catch {
            }
            
            do {
                try fileManager.copyItem(at: location, to: destinationPath)
            }catch let error as NSError {
                print("Could not copy file to disk: \(error.localizedDescription)")
            }
        }
        
        if let remoteUrl = downloadTask.originalRequest?.url?.absoluteString {
            activeDownloads[remoteUrl] = nil
            
            let index = trackIndexForDownloadTask(downloadTask: downloadTask)
            
            if index != -1 {
                DispatchQueue.main.async {
                    self.tableView!.reloadRows(at: [IndexPath(row: index, section: 0)], with: .none)
                }
            }
        }
    }
    
    /* Sent periodically to notify the delegate of download progress. */
    
    func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
        if totalBytesExpectedToWrite < 1 {
            return
        }
        
        let downloadProgress = Float(totalBytesWritten) / Float(totalBytesExpectedToWrite)
        
        if let remoteUrl = downloadTask.originalRequest?.url?.absoluteString, let currentDownload = activeDownloads[remoteUrl] {
            currentDownload.progress = downloadProgress
            currentDownload.totalSizeString = ByteCountFormatter.string(fromByteCount: totalBytesExpectedToWrite, countStyle: .binary)
            
            let index = trackIndexForDownloadTask(downloadTask: downloadTask)
            
            if index != -1 {
                DispatchQueue.main.async {
                    self.tableView!.reloadRows(at: [IndexPath(row: index, section: 0)], with: .none)
                }
            }
        }
    }
    
    /* Sent when a download has been resumed. If a download failed with an
     * error, the -userInfo dictionary of the error will contain an
     * NSURLSessionDownloadTaskResumeData key, whose value is the resume
     * data.
     */
   
    func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didResumeAtOffset fileOffset: Int64, expectedTotalBytes: Int64) {
        
    }
    
    func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
        if let appDelegate = UIApplication.shared.delegate as? AppDelegate{
            if let completionHandler = appDelegate.backgroundSessionCompletionHandler {
                appDelegate.backgroundSessionCompletionHandler = nil
                DispatchQueue.main.async {
                    completionHandler()
                }
            }
        }
    }
}
